A thesaurus is not a species of dinosaur

Tidy eval with dynamic filters

January 04, 2023

Tidyverse with its many packages has certainly made life easier for a lot of Data scientists.

I had a peculiar problem which led me down in to a rabbit hole of tidyeval , the bang-bang operator all of which was new to me. Maybe this is helpful for someone.

Suppose you have a dataframe (or a tibble) and there are multiple plots( in this case echarts from echarts4r package) which could be used to click . We would like to display a filtered table based on which selections were made. Essentially, the filter portion of the code is dynamic

for example dataframe1 |> filter(condition1, condition2,..) and conditions are of the form col1 == someval etc

The way to do it is to create expressions as strings and then evaluate them later. pls refer tidyeval guide or rlang

First we create a list of the chart click inputs so that we can get data ( this entire code will be wrapped in a reactive later)

temp1 <- setnames(list(input$chart1_clicked_data, input$chart2_clicked_data),list("chart1_variable","chart2variable"))

Now if the chart is left unclicked, the corresponding input is NULL, so we remove that in the next step

temp1 <- temp1[lengths(temp1) != 0]

Now we need to somehow use both the chart1_variable as the right side of the filter expression and the values inside the inputs on the right side.

using imap from purrr

temp3 <-  imap(temp1, function(x,y) {

    expr(!!sym(y)) == !!x$Value)
}

Here, y is the name of the list element and x holds the values as returned. !! causes it to be evaluated inside the expr(). We use !!sym(y) because we want the unquoted value in the filter expression i.e. chart1variable == “xx” not “chart1variable” == “xx”

temp3 now holds a list of expressions of the form [“chart1_variable == 1, ..] , but it’s a named list so we remove names in the next step.

names(temp3)  <- NULL

we can now evaluate all the expressions at once.

eval(dataframe1 |> filter(!!!temp3))

The !!! parses the list correctly

This entire code is to be inside a reactive function so that it looks like this

data_filtered <- reactive({

temp1 <- setnames(list(input$chart1_clicked_data, input$chart2_clicked_data),list("chart1_variable","chart2variable"))
temp1 <- temp1[lengths(temp1) != 0]
temp3 <-  imap(temp1, function(x,y) {

    expr(!!sym(y)) == !!x$Value
}
names(temp3)  <- NULL
eval(dataframe1 |> filter(!!!temp3))
})

Now we could add conditionals to check for length of temp1 so that no filtering happens if nothing is clicked etc.


Narayanan Iyer

Written by Narayanan Iyer who lives and works in Mumbai. Full time R and shiny enthusiast , he spends way too much time on HN. Fluent in R,shiny, docker, Python, Javascript and C# and 6 other human languages.

You can contact him at narayanan iyer 22 at gmail dot com